package controller

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"webssh/src/logger"
	"webssh/src/response"

	"github.com/gin-gonic/gin"
)

func uploadTempPath(uploadPath string) string {
	return path.Join(uploadPath, "temp")
}

// UploadBigFile 上传文件
func UploadBigFile(c *gin.Context) *response.ResponseBody {
	r := c.Request
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")
	file, _, err := r.FormFile("file")
	if err != nil {
		logger.Info(err.Error())
		return response.ErrReadFile
	}
	index := r.PostFormValue("index")
	hash := r.PostFormValue("hash")
	uploadPath := r.PostFormValue("path")
	tenantPath := tenantHomePath(tenantId)
	if !strings.HasPrefix(uploadPath, tenantPath) {
		// 默认上传在用户工作目录下
		uploadPath = getUserHomeDir(tenantId, username)
	}

	uploadTempPath := uploadTempPath(uploadPath)
	// 获取uploads下所有的文件夹
	nameList, err := os.ReadDir(uploadPath)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrReadFile
	}
	// 循环判断hash是否在文件里如果有就返回上传已完成
	for _, name := range nameList {
		tmpName := strings.Split(name.Name(), "_")[0]
		if tmpName == hash {
			return response.SuccessResponse
		}
	}
	chunksPath := path.Join(uploadTempPath, hash, "/")
	isPathExists, err := PathExists(chunksPath)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrReadFile
	}
	if !isPathExists {
		err = os.MkdirAll(chunksPath, os.ModePerm)
		if err != nil {
			logger.Info(err.Error())
			return response.ErrCreateFile
		}
	}
	destFile, err := os.OpenFile(path.Join(chunksPath+"/"+hash+"-"+index), syscall.O_CREAT|syscall.O_WRONLY, 0777)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrReadFile
	}
	reader := bufio.NewReader(file)
	writer := bufio.NewWriter(destFile)
	buf := make([]byte, 1024*1024) // 1M buf
	for {
		n, err := reader.Read(buf)
		if err == io.EOF {
			writer.Flush()
			break
		} else if err != nil {
			logger.Info(err.Error())
			return response.ErrReadFile
		} else {
			writer.Write(buf[:n])
		}
	}
	defer file.Close()
	defer destFile.Close()
	fmt.Printf("第%s:%s块上传完成\n", index, destFile.Name())
	return response.SuccessResponse
}

// Chunks 合并大文件
func Chunks(c *gin.Context) *response.ResponseBody {
	r := c.Request
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")
	uploadPath := r.PostFormValue("path")
	tenantPath := tenantHomePath(tenantId)
	if !strings.HasPrefix(uploadPath, tenantPath) {
		// 默认上传在用户工作目录下
		uploadPath = getUserHomeDir(tenantId, username)
	}
	uploadTempPath := uploadTempPath(uploadPath)
	size, _ := strconv.ParseInt(r.PostFormValue("size"), 10, 64)
	hash := r.PostFormValue("hash")
	name := r.PostFormValue("name")
	toSize, _ := getDirSize(path.Join(uploadTempPath, hash, "/"))
	if size != toSize {
		logger.Info("upload big file failed, size not match")
		return response.ErrReadFile
	}
	chunksPath := path.Join(uploadTempPath, hash, "/")
	files, _ := os.ReadDir(chunksPath)
	// 排序
	filesSort := make(map[string]string)
	for _, f := range files {
		nameArr := strings.Split(f.Name(), "-")
		filesSort[nameArr[1]] = f.Name()
	}
	saveFile := path.Join(uploadPath, name)
	if exists, _ := PathExists(saveFile); exists {
		os.Remove(saveFile)
	}
	fs, _ := os.OpenFile(saveFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, os.ModeAppend|os.ModePerm)
	var wg sync.WaitGroup
	filesCount := len(files)
	if filesCount != len(filesSort) {
		logger.Info("upload big file failed, size not match")
		return response.ErrUploadFile
	}
	wg.Add(filesCount)
	for i := 0; i < filesCount; i++ {
		// 这里一定要注意按顺序读取不然文件就会损坏
		fileName := path.Join(chunksPath, "/"+filesSort[strconv.Itoa(i)])
		data, err := os.ReadFile(fileName)
		fmt.Println(err)
		fs.Write(data)
		wg.Done()
	}
	wg.Wait()
	defer fs.Close()
	if err := setFileChown(saveFile, username); err != nil {
		logger.Info("set file chown err, %s", err.Error())
	}
	if err := os.RemoveAll(path.Join(uploadTempPath, "/")); err != nil {
		logger.Info("remove temp file err, %s", err.Error())
	}
	return response.SuccessResponse
}

func setFileChown(filePath, userName string) error {
	u, err := user.Lookup(userName)
	if err != nil {
		return fmt.Errorf("error looking up user %s: %s", userName, err.Error())
	}
	uid, err := strconv.Atoi(u.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(u.Gid)
	if err != nil {
		return err
	}
	if err := os.Chown(filePath, uid, gid); err != nil {
		return err
	}
	return nil
}

// 获取整体文件夹大小
func getDirSize(path string) (int64, error) {
	var size int64
	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			size += info.Size()
		}
		return err
	})
	return size, err
}

// PathExists 判断文件夹是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
